package com.tnovoselec.android.ui;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;


public class MySlidingDrawer extends ViewGroup {

    public static final int LEFT = 0;
    public static final int TOP = 1;
    public static final int RIGHT = 2;
    public static final int BOTTOM = 3;

    private static final int TAP_THRESHOLD = 6;
    private static final float MAXIMUM_TAP_VELOCITY = 100.0f;
    private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;
    private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
    private static final float MAXIMUM_ACCELERATION = 2000.0f;
    private static final int VELOCITY_UNITS = 1000;
    private static final int MSG_ANIMATE = 1000;
    private static final int ANIMATION_FRAME_DURATION = 1000 / 60;

    private static final int EXPANDED_FULL_OPEN = -10001;
    private static final int COLLAPSED_FULL_CLOSED = -10002;

    private final int mHandleId;
    private final int mContentId;

    private View mHandle;
    private View mContent;

    private final Rect mFrame = new Rect();
    private final Rect mInvalidate = new Rect();
    private boolean mTracking;
    private boolean mLocked;

    private VelocityTracker mVelocityTracker;

    private boolean mInvert;
    private boolean mVertical;
    private boolean mExpanded;
    private int mBottomOffset;
    private int mTopOffset;
    private int mHandleHeight;
    private int mHandleWidth;

    private OnDrawerOpenListener mOnDrawerOpenListener;
    private OnDrawerCloseListener mOnDrawerCloseListener;
    private OnDrawerScrollListener mOnDrawerScrollListener;

    private final Handler mHandler = new SlidingHandler();
    private float mAnimatedAcceleration;
    private float mAnimatedVelocity;
    private float mAnimationPosition;
    private long mAnimationLastTime;
    private long mCurrentAnimationTime;
    private int mTouchDelta;
    private boolean mAnimating;
    private boolean mAllowSingleTap;
    private boolean mAnimateOnClick;

    private final int mTapThreshold;
    private final int mMaximumTapVelocity;
    private int mMaximumMinorVelocity;
    private int mMaximumMajorVelocity;
    private int mMaximumAcceleration;
    private final int mVelocityUnits;

    /**
     * Callback invoked when the drawer is opened.
     */
    public static interface OnDrawerOpenListener {

	/**
	 * Invoked when the drawer becomes fully open.
	 */
	public void onDrawerOpened();
    }

    /**
     * Callback invoked when the drawer is closed.
     */
    public static interface OnDrawerCloseListener {

	/**
	 * Invoked when the drawer becomes fully closed.
	 */
	public void onDrawerClosed();
    }

    /**
     * Callback invoked when the drawer is scrolled.
     */
    public static interface OnDrawerScrollListener {

	/**
	 * Invoked when the user starts dragging/flinging the drawer's handle.
	 */
	public void onScrollStarted();

	/**
	 * Invoked when the user stops dragging/flinging the drawer's handle.
	 */
	public void onScrollEnded();
    }

    /**
     * Creates a new SlidingDrawer from a specified set of attributes defined in
     * XML.
     * 
     * @param context
     *            The application's environment.
     * @param attrs
     *            The attributes defined in XML.
     */
    public MySlidingDrawer(Context context, AttributeSet attrs) {
	this(context, attrs, 0);
    }

    /**
     * Creates a new SlidingDrawer from a specified set of attributes defined in
     * XML.
     * 
     * @param context
     *            The application's environment.
     * @param attrs
     *            The attributes defined in XML.
     * @param defStyle
     *            The style to apply to this widget.
     */
    public MySlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
	super(context, attrs, defStyle);
	TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MySlidingDrawer, defStyle, 0);

	int orientation = a.getInt(R.styleable.MySlidingDrawer_orientation, TOP);
	mBottomOffset = (int) a.getDimension(R.styleable.MySlidingDrawer_bottomOffset, 0.0f);
	mTopOffset = (int) a.getDimension(R.styleable.MySlidingDrawer_topOffset, 0.0f);
	mAllowSingleTap = a.getBoolean(R.styleable.MySlidingDrawer_allowSingleTap, true);
	mAnimateOnClick = a.getBoolean(R.styleable.MySlidingDrawer_animateOnClick, true);
	mVertical = (orientation == BOTTOM || orientation == TOP);
	mInvert = (orientation == TOP || orientation == LEFT);

	int handleId = a.getResourceId(R.styleable.MySlidingDrawer_handle, 0);
	if (handleId == 0) {
	    throw new IllegalArgumentException("The handle attribute is required and must refer " + "to a valid child.");
	}

	int contentId = a.getResourceId(R.styleable.MySlidingDrawer_content, 0);
	if (contentId == 0) {
	    throw new IllegalArgumentException("The content attribute is required and must refer " + "to a valid child.");
	}

	if (handleId == contentId) {
	    throw new IllegalArgumentException("The content and handle attributes must refer " + "to different children.");
	}
	mHandleId = handleId;
	mContentId = contentId;

	final float density = getResources().getDisplayMetrics().density;
	mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
	mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
	mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
	mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
	mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
	mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);

	if (mInvert) {
	    mMaximumAcceleration = -mMaximumAcceleration;
	    // mMaximumMajorVelocity = -mMaximumMajorVelocity;
	    // mMaximumMinorVelocity = -mMaximumMinorVelocity;
	}

	a.recycle();
	setAlwaysDrawnWithCacheEnabled(false);
    }

    @Override
    protected void onFinishInflate() {
	mHandle = findViewById(mHandleId);
	if (mHandle == null) {
	    throw new IllegalArgumentException("The handle attribute is must refer to an" + " existing child.");
	}
	mHandle.setOnClickListener(new DrawerToggler());

	mContent = findViewById(mContentId);
	if (mContent == null) {
	    throw new IllegalArgumentException("The content attribute is must refer to an" + " existing child.");
	}
	mContent.setVisibility(View.GONE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
	int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

	int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
	int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

	if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
	    throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
	}

	final View handle = mHandle;
	measureChild(handle, widthMeasureSpec, heightMeasureSpec);

	if (mVertical) {
	    int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
	    mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
	} else {
	    int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
	    mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));
	}

	setMeasuredDimension(widthSpecSize, heightSpecSize);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
	final long drawingTime = getDrawingTime();
	final View handle = mHandle;
	final boolean isVertical = mVertical;

	drawChild(canvas, handle, drawingTime);

	if (mTracking || mAnimating) {
	    final Bitmap cache = mContent.getDrawingCache();
	    if (cache != null) {
		if (isVertical) {
		    if (mInvert) {
			canvas.drawBitmap(cache, 0, handle.getTop() - (getBottom() - getTop()) + mHandleHeight, null);
		    } else {
			canvas.drawBitmap(cache, 0, handle.getBottom(), null);
		    }
		} else {
		    canvas.drawBitmap(cache, mInvert ? handle.getLeft() - cache.getWidth() : handle.getRight(), 0, null);
		}
	    } else {
		canvas.save();
		if (mInvert) {
		    canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset - mContent.getMeasuredWidth(), isVertical ? handle.getTop() - mTopOffset - mContent.getMeasuredHeight() : 0);
		} else {
		    canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset, isVertical ? handle.getTop() - mTopOffset : 0);
		}
		drawChild(canvas, mContent, drawingTime);
		canvas.restore();
	    }
	    invalidate();
	} else if (mExpanded) {
	    drawChild(canvas, mContent, drawingTime);
	}
    }

    public static final String LOG_TAG = "Sliding";

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
	if (mTracking) {
	    return;
	}

	final int width = r - l;
	final int height = b - t;

	final View handle = mHandle;

	int handleWidth = handle.getMeasuredWidth();
	int handleHeight = handle.getMeasuredHeight();

	Log.d(LOG_TAG, "handleHeight: " + handleHeight);

	int handleLeft;
	int handleTop;

	final View content = mContent;

	if (mVertical) {
	    handleLeft = (width - handleWidth) / 2;
	    if (mInvert) {
		Log.d(LOG_TAG, "content.layout(1)");
		handleTop = mExpanded ? height - mBottomOffset - handleHeight : mTopOffset;
		content.layout(0, mTopOffset, content.getMeasuredWidth(), mTopOffset + content.getMeasuredHeight());
	    } else {
		handleTop = mExpanded ? mTopOffset : height - handleHeight + mBottomOffset;
		content.layout(0, mTopOffset + handleHeight, content.getMeasuredWidth(), mTopOffset + handleHeight + content.getMeasuredHeight());
	    }
	} else {
	    handleTop = (height - handleHeight) / 2;
	    if (mInvert) {
		handleLeft = mExpanded ? width - mBottomOffset - handleWidth : mTopOffset;
		content.layout(mTopOffset, 0, mTopOffset + content.getMeasuredWidth(), content.getMeasuredHeight());
	    } else {
		handleLeft = mExpanded ? mTopOffset : width - handleWidth + mBottomOffset;
		content.layout(mTopOffset + handleWidth, 0, mTopOffset + handleWidth + content.getMeasuredWidth(), content.getMeasuredHeight());
	    }
	}

	handle.layout(handleLeft, handleTop, handleLeft + handleWidth, handleTop + handleHeight);
	mHandleHeight = handle.getHeight();
	mHandleWidth = handle.getWidth();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
	if (mLocked) {
	    return false;
	}

	final int action = event.getAction();

	float x = event.getX();
	float y = event.getY();

	final Rect frame = mFrame;
	final View handle = mHandle;

	handle.getHitRect(frame);
	if (!mTracking && !frame.contains((int) x, (int) y)) {
	    return false;
	}

	if (action == MotionEvent.ACTION_DOWN) {
	    mTracking = true;

	    handle.setPressed(true);
	    // Must be called before prepareTracking()
	    prepareContent();

	    // Must be called after prepareContent()
	    if (mOnDrawerScrollListener != null) {
		mOnDrawerScrollListener.onScrollStarted();
	    }

	    if (mVertical) {
		final int top = mHandle.getTop();
		mTouchDelta = (int) y - top;
		prepareTracking(top);
	    } else {
		final int left = mHandle.getLeft();
		mTouchDelta = (int) x - left;
		prepareTracking(left);
	    }
	    mVelocityTracker.addMovement(event);
	}

	return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
	if (mLocked) {
	    return true;
	}

	if (mTracking) {
	    mVelocityTracker.addMovement(event);
	    final int action = event.getAction();
	    switch (action) {
		case MotionEvent.ACTION_MOVE:
		    moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
		    break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL: {
		    final VelocityTracker velocityTracker = mVelocityTracker;
		    velocityTracker.computeCurrentVelocity(mVelocityUnits);

		    float yVelocity = velocityTracker.getYVelocity();
		    float xVelocity = velocityTracker.getXVelocity();
		    boolean negative;

		    final boolean vertical = mVertical;
		    if (vertical) {
			negative = yVelocity < 0;
			if (xVelocity < 0) {
			    xVelocity = -xVelocity;
			}
			if (xVelocity > mMaximumMinorVelocity) {
			    xVelocity = mMaximumMinorVelocity;
			}
		    } else {
			negative = xVelocity < 0;
			if (yVelocity < 0) {
			    yVelocity = -yVelocity;
			}
			if (yVelocity > mMaximumMinorVelocity) {
			    yVelocity = mMaximumMinorVelocity;
			}
		    }

		    float velocity = (float) Math.hypot(xVelocity, yVelocity);
		    if (negative) {
			velocity = -velocity;
		    }

		    final int handleTop = mHandle.getTop();
		    final int handleLeft = mHandle.getLeft();

		    if (Math.abs(velocity) < mMaximumTapVelocity) {
			if (inThreshold(handleTop, handleLeft)) {

			    if (mAllowSingleTap) {
				playSoundEffect(SoundEffectConstants.CLICK);

				if (mExpanded) {
				    animateClose(vertical ? handleTop : handleLeft);
				} else {
				    animateOpen(vertical ? handleTop : handleLeft);
				}
			    } else {
				performFling(vertical ? handleTop : handleLeft, velocity, false);
			    }
			} else {
			    performFling(vertical ? handleTop : handleLeft, velocity, false);
			}
		    } else {
			performFling(vertical ? handleTop : handleLeft, velocity, false);
		    }
		}
		    break;
	    }
	}

	return mTracking || mAnimating || super.onTouchEvent(event);
    }

    //////////////////////////////////////// tha fuck?
    @Override
    public boolean isFocused() {
	return isOpened();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
	if (keyCode == KeyEvent.KEYCODE_BACK && isOpened()) {
	    animateClose();
	    return true;
	}
	return super.onKeyDown(keyCode, event);
    }
    ///////////////////////////////////////////////////

    private boolean inThreshold(int top, int left) {
	if (mVertical) {
	    if (mInvert) {
		return (!mExpanded && top < mTapThreshold - mBottomOffset) || (mExpanded && top > getBottom() - getTop() - mHandleHeight - mTopOffset - mTapThreshold);
	    } else {
		return (mExpanded && top < mTapThreshold + mTopOffset) || (!mExpanded && top > mBottomOffset + getBottom() - getTop() - mHandleHeight - mTapThreshold);
	    }
	} else {
	    if (mInvert) {
		return (!mExpanded && left < mTapThreshold - mBottomOffset) || (mExpanded && left > getRight() - getLeft() - mHandleWidth - mTopOffset - mTapThreshold);
	    } else {
		return (mExpanded && left < mTapThreshold + mTopOffset) || (!mExpanded && left > mBottomOffset + getRight() - getLeft() - mHandleWidth - mTapThreshold);
	    }
	}
    }

    private void animateClose(int position) {
	prepareTracking(position);
	performFling(position, mMaximumAcceleration, true);
    }

    private void animateOpen(int position) {
	prepareTracking(position);
	performFling(position, -mMaximumAcceleration, true);
    }

    private void performFling(int position, float velocity, boolean always) {
	mAnimationPosition = position;
	mAnimatedVelocity = velocity;

	boolean c1;
	boolean c2;
	boolean c3;

	if (mExpanded) {
	    int bottom = mVertical ? getBottom() : getRight();
	    int handleHeight = mVertical ? mHandleHeight : mHandleWidth;

	    Log.d(LOG_TAG, "position: " + position + ", velocity: " + velocity + ", mMaximumMajorVelocity: " + mMaximumMajorVelocity);
	    c1 = mInvert ? velocity < mMaximumMajorVelocity : velocity > mMaximumMajorVelocity;
	    c2 = mInvert ? (bottom - (position + handleHeight)) + mBottomOffset > handleHeight : position > mTopOffset + (mVertical ? mHandleHeight : mHandleWidth);
	    c3 = mInvert ? velocity < -mMaximumMajorVelocity : velocity > -mMaximumMajorVelocity;
	    Log.d(LOG_TAG, "EXPANDED. c1: " + c1 + ", c2: " + c2 + ", c3: " + c3);
	    if (always || (c1 || (c2 && c3))) {
		// We are expanded, So animate to CLOSE!
		mAnimatedAcceleration = mMaximumAcceleration;
		if (mInvert) {
		    if (velocity > 0) {
			mAnimatedVelocity = 0;
		    }
		} else {
		    if (velocity < 0) {
			mAnimatedVelocity = 0;
		    }
		}
	    } else {
		// We are expanded, but they didn't move sufficiently to cause
		// us to retract. Animate back to the expanded position. so
		// animate BACK to expanded!
		mAnimatedAcceleration = -mMaximumAcceleration;

		if (mInvert) {
		    if (velocity < 0) {
			mAnimatedVelocity = 0;
		    }
		} else {
		    if (velocity > 0) {
			mAnimatedVelocity = 0;
		    }
		}
	    }
	} else {

	    // WE'RE COLLAPSED

	    c1 = mInvert ? velocity < mMaximumMajorVelocity : velocity > mMaximumMajorVelocity;
	    c2 = mInvert ? (position < (mVertical ? getHeight() : getWidth()) / 2) : (position > (mVertical ? getHeight() : getWidth()) / 2);
	    c3 = mInvert ? velocity < -mMaximumMajorVelocity : velocity > -mMaximumMajorVelocity;

	    Log.d(LOG_TAG, "COLLAPSED. position: " + position + ", velocity: " + velocity + ", mMaximumMajorVelocity: " + mMaximumMajorVelocity);
	    Log.d(LOG_TAG, "COLLAPSED. always: " + always + ", c1: " + c1 + ", c2: " + c2 + ", c3: " + c3);

	    if (!always && (c1 || (c2 && c3))) {
		mAnimatedAcceleration = mMaximumAcceleration;

		if (mInvert) {
		    if (velocity > 0) {
			mAnimatedVelocity = 0;
		    }
		} else {
		    if (velocity < 0) {
			mAnimatedVelocity = 0;
		    }
		}
	    } else {
		mAnimatedAcceleration = -mMaximumAcceleration;

		if (mInvert) {
		    if (velocity < 0) {
			mAnimatedVelocity = 0;
		    }
		} else {
		    if (velocity > 0) {
			mAnimatedVelocity = 0;
		    }
		}
	    }
	}

	long now = SystemClock.uptimeMillis();
	mAnimationLastTime = now;
	mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
	mAnimating = true;
	mHandler.removeMessages(MSG_ANIMATE);
	mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
	stopTracking();
    }

    private void prepareTracking(int position) {
	mTracking = true;
	mVelocityTracker = VelocityTracker.obtain();
	boolean opening = !mExpanded;

	if (opening) {
	    mAnimatedAcceleration = mMaximumAcceleration;
	    mAnimatedVelocity = mMaximumMajorVelocity;
	    if (mInvert)
		mAnimationPosition = mTopOffset;
	    else
		mAnimationPosition = mBottomOffset + (mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth);
	    moveHandle((int) mAnimationPosition);
	    mAnimating = true;
	    mHandler.removeMessages(MSG_ANIMATE);
	    long now = SystemClock.uptimeMillis();
	    mAnimationLastTime = now;
	    mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
	    mAnimating = true;
	} else {
	    if (mAnimating) {
		mAnimating = false;
		mHandler.removeMessages(MSG_ANIMATE);
	    }
	    moveHandle(position);
	}
    }

    private void moveHandle(int position) {
	final View handle = mHandle;

	if (mVertical) {
	    if (position == EXPANDED_FULL_OPEN) {
		if (mInvert)
		    handle.offsetTopAndBottom(mBottomOffset + getBottom() - getTop() - mHandleHeight);
		else
		    handle.offsetTopAndBottom(mTopOffset - handle.getTop());
		invalidate();
	    } else if (position == COLLAPSED_FULL_CLOSED) {
		if (mInvert) {
		    handle.offsetTopAndBottom(mTopOffset - handle.getTop());
		} else {
		    handle.offsetTopAndBottom(mBottomOffset + getBottom() - getTop() - mHandleHeight - handle.getTop());
		}
		invalidate();
	    } else {
		final int top = handle.getTop();
		int deltaY = position - top;
		if (position < mTopOffset) {
		    deltaY = mTopOffset - top;
		} else if (deltaY > mBottomOffset + getBottom() - getTop() - mHandleHeight - top) {
		    deltaY = mBottomOffset + getBottom() - getTop() - mHandleHeight - top;
		}

		handle.offsetTopAndBottom(deltaY);

		final Rect frame = mFrame;
		final Rect region = mInvalidate;

		handle.getHitRect(frame);
		region.set(frame);

		region.union(frame.left, frame.top - deltaY, frame.right, frame.bottom - deltaY);
		region.union(0, frame.bottom - deltaY, getWidth(), frame.bottom - deltaY + mContent.getHeight());

		invalidate(region);
	    }
	} else {
	    if (position == EXPANDED_FULL_OPEN) {
		if (mInvert)
		    handle.offsetLeftAndRight(mBottomOffset + getRight() - getLeft() - mHandleWidth);
		else
		    handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
		invalidate();
	    } else if (position == COLLAPSED_FULL_CLOSED) {
		if (mInvert)
		    handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
		else
		    handle.offsetLeftAndRight(mBottomOffset + getRight() - getLeft() - mHandleWidth - handle.getLeft());
		invalidate();
	    } else {
		final int left = handle.getLeft();
		int deltaX = position - left;
		if (position < mTopOffset) {
		    deltaX = mTopOffset - left;
		} else if (deltaX > mBottomOffset + getRight() - getLeft() - mHandleWidth - left) {
		    deltaX = mBottomOffset + getRight() - getLeft() - mHandleWidth - left;
		}
		handle.offsetLeftAndRight(deltaX);

		final Rect frame = mFrame;
		final Rect region = mInvalidate;

		handle.getHitRect(frame);
		region.set(frame);

		region.union(frame.left - deltaX, frame.top, frame.right - deltaX, frame.bottom);
		region.union(frame.right - deltaX, 0, frame.right - deltaX + mContent.getWidth(), getHeight());

		invalidate(region);
	    }
	}
    }

    private void prepareContent() {
	if (mAnimating) {
	    return;
	}

	// Something changed in the content, we need to honor the layout request
	// before creating the cached bitmap
	final View content = mContent;
	if (content.isLayoutRequested()) {

	    if (mVertical) {
		final int handleHeight = mHandleHeight;
		int height = getBottom() - getTop() - handleHeight - mTopOffset;
		content.measure(MeasureSpec.makeMeasureSpec(getRight() - getLeft(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

		Log.d(LOG_TAG, "content.layout(2)");

		if (mInvert)
		    content.layout(0, mTopOffset, content.getMeasuredWidth(), mTopOffset + content.getMeasuredHeight());
		else
		    content.layout(0, mTopOffset + handleHeight, content.getMeasuredWidth(), mTopOffset + handleHeight + content.getMeasuredHeight());

	    } else {

		final int handleWidth = mHandle.getWidth();
		int width = getRight() - getLeft() - handleWidth - mTopOffset;
		content.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getBottom() - getTop(), MeasureSpec.EXACTLY));

		if (mInvert)
		    content.layout(mTopOffset, 0, mTopOffset + content.getMeasuredWidth(), content.getMeasuredHeight());
		else
		    content.layout(handleWidth + mTopOffset, 0, mTopOffset + handleWidth + content.getMeasuredWidth(), content.getMeasuredHeight());
	    }
	}
	// Try only once... we should really loop but it's not a big deal
	// if the draw was cancelled, it will only be temporary anyway
	content.getViewTreeObserver().dispatchOnPreDraw();
	content.buildDrawingCache();

	content.setVisibility(View.GONE);
    }

    private void stopTracking() {
	mHandle.setPressed(false);
	mTracking = false;

	if (mOnDrawerScrollListener != null) {
	    mOnDrawerScrollListener.onScrollEnded();
	}

	if (mVelocityTracker != null) {
	    mVelocityTracker.recycle();
	    mVelocityTracker = null;
	}
    }

    private void doAnimation() {
	if (mAnimating) {
	    incrementAnimation();

	    if (mInvert) {
		if (mAnimationPosition < mTopOffset) {
		    mAnimating = false;
		    closeDrawer();
		} else if (mAnimationPosition >= mTopOffset + (mVertical ? getHeight() : getWidth()) - 1) {
		    mAnimating = false;
		    openDrawer();
		} else {
		    moveHandle((int) mAnimationPosition);
		    mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
		    mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
		}
	    } else {
		if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) {
		    mAnimating = false;
		    closeDrawer();
		} else if (mAnimationPosition < mTopOffset) {
		    mAnimating = false;
		    openDrawer();
		} else {
		    moveHandle((int) mAnimationPosition);
		    mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
		    mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
		}
	    }
	}
    }

    private void incrementAnimation() {
	long now = SystemClock.uptimeMillis();
	float t = (now - mAnimationLastTime) / 1000.0f; // ms -> s
	final float position = mAnimationPosition;
	final float v = mAnimatedVelocity; // px/s
	final float a = mInvert ? mAnimatedAcceleration : mAnimatedAcceleration; // px/s/s
	mAnimationPosition = position + (v * t) + (0.5f * a * t * t); // px
	mAnimatedVelocity = v + (a * t); // px/s
	mAnimationLastTime = now; // ms
    }

    /**
     * Toggles the drawer open and close. Takes effect immediately.
     * 
     * @see #open()
     * @see #close()
     * @see #animateClose()
     * @see #animateOpen()
     * @see #animateToggle()
     */
    public void toggle() {
	if (!mExpanded) {
	    openDrawer();
	} else {
	    closeDrawer();
	}
	invalidate();
	requestLayout();
    }

    /**
     * Toggles the drawer open and close with an animation.
     * 
     * @see #open()
     * @see #close()
     * @see #animateClose()
     * @see #animateOpen()
     * @see #toggle()
     */
    public void animateToggle() {
	if (!mExpanded) {
	    animateOpen();
	} else {
	    animateClose();
	}
    }

    /**
     * Opens the drawer immediately.
     * 
     * @see #toggle()
     * @see #close()
     * @see #animateOpen()
     */
    public void open() {
	openDrawer();
	invalidate();
	requestLayout();

	sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    }

    /**
     * Closes the drawer immediately.
     * 
     * @see #toggle()
     * @see #open()
     * @see #animateClose()
     */
    public void close() {
	closeDrawer();
	invalidate();
	requestLayout();
    }

    /**
     * Closes the drawer with an animation.
     * 
     * @see #close()
     * @see #open()
     * @see #animateOpen()
     * @see #animateToggle()
     * @see #toggle()
     */
    public void animateClose() {
	prepareContent();
	final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
	if (scrollListener != null) {
	    scrollListener.onScrollStarted();
	}
	animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft());

	if (scrollListener != null) {
	    scrollListener.onScrollEnded();
	}
    }

    /**
     * Opens the drawer with an animation.
     * 
     * @see #close()
     * @see #open()
     * @see #animateClose()
     * @see #animateToggle()
     * @see #toggle()
     */
    public void animateOpen() {
	prepareContent();
	final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
	if (scrollListener != null) {
	    scrollListener.onScrollStarted();
	}
	animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft());

	sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);

	if (scrollListener != null) {
	    scrollListener.onScrollEnded();
	}
    }

    private void closeDrawer() {
	moveHandle(COLLAPSED_FULL_CLOSED);
	mContent.setVisibility(View.GONE);
	mContent.destroyDrawingCache();

	if (!mExpanded) {
	    return;
	}

	mExpanded = false;
	if (mOnDrawerCloseListener != null) {
	    mOnDrawerCloseListener.onDrawerClosed();
	}
    }

    private void openDrawer() {
	moveHandle(EXPANDED_FULL_OPEN);
	mContent.setVisibility(View.VISIBLE);

	if (mExpanded) {
	    return;
	}

	mExpanded = true;

	if (mOnDrawerOpenListener != null) {
	    mOnDrawerOpenListener.onDrawerOpened();
	}
    }

    /**
     * Sets the listener that receives a notification when the drawer becomes
     * open.
     * 
     * @param onDrawerOpenListener
     *            The listener to be notified when the drawer is opened.
     */
    public void setOnDrawerOpenListener(OnDrawerOpenListener onDrawerOpenListener) {
	mOnDrawerOpenListener = onDrawerOpenListener;
    }

    /**
     * Sets the listener that receives a notification when the drawer becomes
     * close.
     * 
     * @param onDrawerCloseListener
     *            The listener to be notified when the drawer is closed.
     */
    public void setOnDrawerCloseListener(OnDrawerCloseListener onDrawerCloseListener) {
	mOnDrawerCloseListener = onDrawerCloseListener;
    }

    /**
     * Sets the listener that receives a notification when the drawer starts or
     * ends a scroll. A fling is considered as a scroll. A fling will also
     * trigger a drawer opened or drawer closed event.
     * 
     * @param onDrawerScrollListener
     *            The listener to be notified when scrolling starts or stops.
     */
    public void setOnDrawerScrollListener(OnDrawerScrollListener onDrawerScrollListener) {
	mOnDrawerScrollListener = onDrawerScrollListener;
    }

    /**
     * Returns the handle of the drawer.
     * 
     * @return The View reprenseting the handle of the drawer, identified by the
     *         "handle" id in XML.
     */
    public View getHandle() {
	return mHandle;
    }

    /**
     * Returns the content of the drawer.
     * 
     * @return The View reprenseting the content of the drawer, identified by
     *         the "content" id in XML.
     */
    public View getContent() {
	return mContent;
    }

    /**
     * Unlocks the SlidingDrawer so that touch events are processed.
     * 
     * @see #lock()
     */
    public void unlock() {
	mLocked = false;
    }

    /**
     * Locks the SlidingDrawer so that touch events are ignores.
     * 
     * @see #unlock()
     */
    public void lock() {
	mLocked = true;
    }

    /**
     * Indicates whether the drawer is currently fully opened.
     * 
     * @return True if the drawer is opened, false otherwise.
     */
    public boolean isOpened() {
	return mExpanded;
    }

    /**
     * Indicates whether the drawer is scrolling or flinging.
     * 
     * @return True if the drawer is scroller or flinging, false otherwise.
     */
    public boolean isMoving() {
	return mTracking || mAnimating;
    }

    private class DrawerToggler implements OnClickListener {

	public void onClick(View v) {
	    if (mLocked) {
		return;
	    }
	    // mAllowSingleTap isn't relevant here; you're *always*
	    // allowed to open/close the drawer by clicking with the
	    // trackball.

	    if (mAnimateOnClick) {
		animateToggle();
	    } else {
		toggle();
	    }
	}
    }

    private class SlidingHandler extends Handler {

	public void handleMessage(Message m) {
	    switch (m.what) {
		case MSG_ANIMATE:
		    doAnimation();
		    break;
	    }
	}
    }

}
